package reit.ass3;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.Logger;


/**
 * The Class RunnableClerk, class that simulates a clerk booking assets for cutomers
 */
class RunnableClerk implements Runnable {

	/** The Constant MAX_DISTANCE. */
	public static final int CLERCK_MAX_DISTANCE_BEFORE_SLEEP = 8;
	/** The logger. */
	protected Logger fLogger;

	/** The clerk details. */
	private ClerkDetails fClerkDetails;

	/** The current rental request. */
	private RentalRequest fCurrentRentalRequest;

	/** The rental requests. */
	private RentalRequests fRentalRequests;

	/** The assets. */
	private AssetStorageInterface fAssets;

	/** The day finnished. */
	private CyclicBarrier fDayFinnished;

	/** The open requests. */
	private AtomicInteger fOpenRequests;

	/** The distance walked. */
	private int fDistanceWalked;


	/** The clerks can be shut down. */
	private AtomicInteger fClerksCanBeShutDown;

	/**
	 * Instantiates a new runnable clerk.
	 *@post	mClerkDetails is populated by clerkDetails
	 * @param clerkDetails the clerk details
	 */
	private RunnableClerk(final ClerkDetails clerkDetails) {
		fLogger = Logger.getLogger(RunnableClerk.class);
		fCurrentRentalRequest = null;
		fClerkDetails = clerkDetails;

		if (clerkDetails == null) {
			fLogger.error("no clerk details given, null provided instead");
		} else {
			fLogger.info(MyStringBuilder.buildString("Clerk ",
					clerkDetails.getName(), " was instatiated"));
		}
	}


	/**
	 * Instantiates a new runnable clerk.
	 *
	 * @param clerkDetails the clerk details
	 * @param tRentalRequests the rental requests
	 * @param tManagement the management
	 * @param tOpenRequests the open requests
	 * @param tDayFinnished the day finnished
	 * @param clerksCanBeShutDown the clerks can be shut down
	 * @post	mClerkDetails is populated by clerkDetails
	 *@post	mRentalRequests is populated by rentalRequests, ordered by room size, from the largest to the smallest
	 */

	public RunnableClerk(final ClerkDetails clerkDetails,
			final RentalRequests tRentalRequests, final AssetStorageInterface tManagement,
			final AtomicInteger tOpenRequests, final CyclicBarrier tDayFinnished,
			final AtomicInteger clerksCanBeShutDown) {
		this(clerkDetails);
		fClerksCanBeShutDown= clerksCanBeShutDown; 
		this.fDistanceWalked = 0;

		this.fOpenRequests = tOpenRequests;
		fRentalRequests = tRentalRequests;

		this.fDayFinnished = tDayFinnished;
		this.fAssets = tManagement;

		fLogger.info(MyStringBuilder.buildString("Clerk ",
				clerkDetails.getName(), " has ",
				Integer.toString(fRentalRequests.size()),
				" open rental reQuests"));
	}

	/**
	 * Go to asset.
	 *
	 * @param asset the asset
	 * @throws InterruptedException the interrupted exception
	 */
	private void goToAsset(final Asset asset) throws InterruptedException {
		this.fLogger.info(MyStringBuilder.buildString(
				fClerkDetails.getName(),
				" is walking and returning from asset ",
				asset.getName(),
				" ~",
				Long.toString(((long) this.fClerkDetails.getLocation()
						.getDistance(asset.getLocation()).doubleValue()) * 2),
				" seconds"));
		Thread.sleep(((long) this.fClerkDetails.getLocation()
				.getDistance(asset.getLocation()).doubleValue()) * 2000); // the distance between the clerk and the asset us slept for going to and coming back from in seconds
		this.fDistanceWalked += ((this.fClerkDetails.getLocation().getDistance(
				asset.getLocation()).doubleValue()) * 2);
		this.fLogger.info(MyStringBuilder.buildString(fClerkDetails.getName(),
				" is done walking"));
	}

	private void getRentalReuqest() {
		this.fLogger.info(MyStringBuilder.buildString(fClerkDetails.getName(),
				" is waiting for rental request, Total Remaining=",
				Integer.toString(this.fOpenRequests.get())));
		this.fCurrentRentalRequest = this.fRentalRequests.take();
		this.fLogger.info(MyStringBuilder.buildString(fClerkDetails.getName(),
				" found a rental request"));
	}

	/**
	 * Search asset.
	 *
	 * @return the asset
	 * @throws InterruptedException the interrupted exception
	 */
	private Asset searchAsset() throws InterruptedException {
		this.fLogger.info(MyStringBuilder.buildString(fClerkDetails.getName(),
				" searching for asset for request ",
				Integer.toString(this.fCurrentRentalRequest.getId())));
		return this.fAssets.aquireAsset(this.fCurrentRentalRequest);
	}

	/**
	 * Book asset.
	 *
	 * @param asset the asset
	 */
	private void bookAsset(final Asset asset) {
		asset.setStatusTo(Status.BOOKED);
		this.fCurrentRentalRequest.setAsset(asset);

		this.fCurrentRentalRequest.setStatusTo(Status.FULFILLED);
		this.fLogger.info(MyStringBuilder.buildString(fClerkDetails.getName(),
				" has found room ", asset.getName(), " for rental request ",
				Integer.toString(this.fCurrentRentalRequest.getId())));
	}

	/**
	 * Try finish shift.
	 */
	private void tryFinishShift() {
		if (this.fDistanceWalked >= CLERCK_MAX_DISTANCE_BEFORE_SLEEP) {
			try {
				this.fLogger.info(MyStringBuilder.buildString("Clerk ",
						this.fClerkDetails.getName(), " is going to sleep"));
				this.fDayFinnished.await();
				this.fLogger.info(MyStringBuilder.buildString("clerk ",
						this.fClerkDetails.getName(), " was woken up"));
				this.fDistanceWalked=0;
			} catch (InterruptedException | BrokenBarrierException e) {
			}
		}
	}

	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public synchronized void run() {
		Asset asset = null;
		while (this.fOpenRequests.get() > 0) {
			try {

				getRentalReuqest();
				if(this.fCurrentRentalRequest != null){
					asset = searchAsset();
					fClerksCanBeShutDown.incrementAndGet();
					
					goToAsset(asset);
					bookAsset(asset);
					fClerksCanBeShutDown.decrementAndGet();
				}
				tryFinishShift();
			} catch (InterruptedException e) {
			}

		}
		this.fLogger.info(MyStringBuilder.buildString(fClerkDetails.getName(),
				" has finished working!"));
	}
}
